package controllers;

import static play.modules.pdf.PDF.renderPDF;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import models.AppUser;
import models.Client;
import models.Formation;
import models.Language;
import models.Mission;
import models.Nationality;
import models.Notification;
import models.Profile;
import models.ProfileLanguages;
import models.Skill;
import models.UserRole;

import org.allcolor.yahp.converter.IHtmlToPdfTransformer;

import play.Logger;
import play.data.validation.Required;
import play.modules.pdf.PDF.Options;
import play.mvc.Before;
import play.mvc.Controller;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

import controllers.tools.JSONLanguage;
import controllers.tools.Tools;

/**
 * This class represents the controller which manage collaborator profile
 * actions.
 * 
 * @author Elian ORIOU
 * 
 */

public class Collaborator extends Controller {

	private static AppUser _CONNECTED_USER;

	private static models.Collaborator _CURRENT_COLLABORATOR;

	private static boolean canEdit;

	// ///////////////////////////////////////////
	// //// MODEL LOADING
	// ///////////////////////////////////////////

	/**
	 * Set the connected user (retrieved with its login and password)
	 */

	static void setConnectedUser() {
		if (Authentication.isConnected()) {
			// Get the connected user using its login (provided by the security
			// module)
			_CONNECTED_USER = AppUser.find("login = ?",
					Authentication.connected()).first();

			Logger.debug("[COLLABORATOR] %s (%s) is connected on askima !",
					_CONNECTED_USER.getLogin(), _CONNECTED_USER.getRole()
							.name());
		} else {
			Logger.warn("Unauthorized access : no user logged.");
			error("Unauthorized access !");
		}
	}

	/**
	 * Load the collaborator profile from DB before view rendering
	 */

	static void loadProfile(String username) {

		// Check if an user is connected
		if (Authentication.isConnected() == false) {
			Logger.warn("[COLLABORATOR] Unauthorized access : no user logged.");
			error("Unauthorized access !");
		}

		// Get the user associated to the requested profile
		AppUser requestedCollaboratorProfileUser = AppUser.find("login = ?",
				username).first();
		if (requestedCollaboratorProfileUser == null) {
			Logger.warn("[COLLABORATOR] Unauthorized access : user not found in the DB.");
			error("Invalid Request !");
		}

		// 1. Connected user : Collaborator Case
		if (Authentication.check(UserRole.COLLABORATOR.name())) {

			// Get the collaborator associated to the connected user and
			// give it to the view in order to display collaborator data
			_CURRENT_COLLABORATOR = models.Collaborator.find("user = ?",
					requestedCollaboratorProfileUser).first();

			// If the user doesn't have a registered profile
			if (_CURRENT_COLLABORATOR == null) {

				// Create entities
				_CURRENT_COLLABORATOR = new models.Collaborator();
				_CURRENT_COLLABORATOR.setUser(requestedCollaboratorProfileUser);

				Profile profile = new Profile();
				profile.save();

				_CURRENT_COLLABORATOR.setValidProfile(profile);
				_CURRENT_COLLABORATOR.setTmpProfile(profile);
				_CURRENT_COLLABORATOR.save();
			}

			// If the profile is requested by another collaborator : error !
			if (_CURRENT_COLLABORATOR.getUser() != _CONNECTED_USER) {
				Logger.warn("[COLLABORATOR] Unauthorized access : the user doesn't have enough rights.");
				error("Unauthorized access !");
			}

			// Set the read only mode
			canEdit = true;
		}
		// 2. Connected user : RTU Case
		else if (Authentication.check(UserRole.RTU.name())) {
			// Get the collaborator associated to the connected user and
			// give it to the view in order to display collaborator data
			_CURRENT_COLLABORATOR = models.Collaborator.find("user = ?",
					requestedCollaboratorProfileUser).first();

			// Set the read only mode
			canEdit = false;
		}
		// 3. Connected user : Other cases
		else {
			canEdit = false;
			Logger.warn("[COLLABORATOR] Unauthorized access : others.");
			error("Unauthorized access !");
		}

		// Logging
		Logger.debug("[COLLABORATOR] Collaborator object [ID : %s] : Loaded.",
				_CURRENT_COLLABORATOR.getCollaboratorID());
	}

	/**
	 * Load extra data before view rendering
	 */

	static void loadModelElements() {

		// Put all clients into the view
		renderArgs.put("clients", Client.findAll());
		// Put an instance of our tools class into the view
		renderArgs.put("tools", new Tools());
		// Put all collaborator's languages into the view
		renderArgs.put(
				"languages",
				ProfileLanguages.find("profile = ?",
						_CURRENT_COLLABORATOR.getValidProfile()).fetch());
		// Put the connected user object into the view
		renderArgs.put("user", _CONNECTED_USER);
		// Put the collaborator object to the view
		renderArgs.put("collab", _CURRENT_COLLABORATOR);
		// Put the read only mode variable into the view
		renderArgs.put("canEdit", canEdit);

		// Logging
		Logger.debug("[COLLABORATOR] Model elements : Loaded.",
				_CURRENT_COLLABORATOR.getCollaboratorID());
	}

	// ///////////////////////////////////////////
	// //// RENDERING METHODS
	// ///////////////////////////////////////////

	/**
	 * The main rendering method
	 * 
	 * @param username
	 * @param canEdit
	 */

	public static void profile(String username) {

		// Find the connected user
		setConnectedUser();
		// Load the collaborator profile
		loadProfile(username);
		// Load extra data before view rendering
		loadModelElements();
		// Logging
		Logger.debug(
				"[COLLABORATOR] Rendering collaborator (%s) profile page (ReadOnly : %b)...",
				username, !canEdit);

		// Render the profile
		render();
	}

	// //////////////////////////////////////////////////////////
	// //// COLLABORATOR IDENTITY MANAGEMENT (SYNCHRONOUSLY)
	// //////////////////////////////////////////////////////////

	/**
	 * Method which can retrieve data from view in order to update the
	 * collaborator identity
	 * 
	 * @param collaboratorID
	 * @param firstname
	 * @param name
	 * @param address
	 * @param zip
	 * @param city
	 * @param phone
	 * @param email
	 * @param nationality
	 * @param birthday
	 * @param homeTown
	 * @param status
	 * @param credential
	 * @param function
	 * @param activityDuration
	 * @param hiring
	 */

	public static void updateIdentity(@Required int collaboratorID,
			@Required String firstname, @Required String name,
			@Required String address, @Required String zip,
			@Required String city, @Required String phone,
			@Required String email, @Required String nationality,
			@Required String birthday, @Required String homeTown,
			@Required String status, @Required String credential,
			@Required String function, @Required int activityDuration,
			@Required String hiring) {

		// Set dates
		Date birthdate = null;
		if (birthday.isEmpty() == false) {
			try {
				birthdate = Tools._DATE_FORMATTER.parse(birthday);
			} catch (ParseException e) {
				e.printStackTrace();
			}
		}

		Date hiringdate = null;
		if (hiring.isEmpty() == false) {
			try {
				hiringdate = Tools._DATE_FORMATTER.parse(hiring);
			} catch (ParseException f) {
				f.printStackTrace();
			}
		}

		// Sets collaborator data
		models.Collaborator collab = models.Collaborator
				.findById(collaboratorID);
		models.Profile profile = collab.getValidProfile();
		profile.setFirstname(firstname);
		profile.setName(name);
		profile.setAddress(address);
		profile.setZip(zip);
		profile.setCity(city);
		profile.setPhone(phone);
		profile.setEmail(email);

		profile.getNationalities().clear();
		for (String s : nationality.split("\\s")) {
			Nationality nat = Nationality.find("name = ?", s).first();
			profile.addNationality(nat);
		}

		profile.setBirthday(birthdate);
		profile.setHometown(homeTown);
		profile.setStatus(status);
		profile.setCredential(credential);
		profile.setFunction(function);
		profile.setActivityDuration(activityDuration);
		profile.setHiring(hiringdate);

		// Save the profile
		profile.save();

		// Logging
		Logger.debug("[COLLABORATOR] Collaborator (%s) identity : updated.",
				_CURRENT_COLLABORATOR.getUser().getLogin());

		// Redirect the client to the collaborator profile page
		profile(_CONNECTED_USER.getLogin());
	}

	// //////////////////////////////////////////////////////////
	// //// COLLABORATOR MISSIONS MANAGEMENT (SYNCHRONOUSLY)
	// //////////////////////////////////////////////////////////

	/**
	 * Method which can retrieve data from view in order to create a
	 * collaborator mission
	 * 
	 * @param collaboratorID
	 * @param clientID
	 * @param missionTitle
	 * @param missionStartDate
	 * @param missionEndDate
	 * @param missionDescription
	 * @param skillsList
	 */

	public static void addMission(@Required int collaboratorID,
			@Required String clientID, @Required String missionTitle,
			@Required String missionStartDate, String missionEndDate,
			@Required String missionDescription, String missionSkillsList) {

		// 1. Parse dates
		Date startDate = null;
		Date endDate = null;
		try {
			startDate = Tools._DATE_FORMATTER.parse(missionStartDate);
			endDate = Tools._DATE_FORMATTER.parse(missionEndDate);
			if (startDate != null && endDate != null
					&& startDate.before(endDate) == false) {
				error("La date de début doit etre anterieure à la date de commencement de la mission !");
			}
		} catch (ParseException e) {
			e.printStackTrace();
		}

		// 2. Retrieves concerned client
		Client client = Client.findById(Integer.parseInt(clientID));

		// 3. Add the new mission to the collaborator profile
		models.Collaborator collaborator = models.Collaborator
				.findById(collaboratorID);
		models.Mission mission = new models.Mission(missionTitle, startDate,
				endDate, missionDescription, client);

		// 3.1. Add specified skills to the mission
		String[] tempStringList = missionSkillsList.split("[|]");
		for (String skillName : tempStringList) {
			Skill skill = models.Skill.find("name = ?", skillName).first();
			if (skill == null) {
				continue;
			}
			mission.addSkill(skill);
		}
		collaborator.getValidProfile().addMission(mission);

		// 4. Save the entity in order to persists it into the DB
		collaborator.save();

		// 5. Logging
		Logger.debug(
				"[COLLABORATOR] Collaborator (%s) missions : one more added (title : %s).",
				_CURRENT_COLLABORATOR.getUser().getLogin(), missionTitle);

		// 6. Redirect the client to the collaborator profile page
		profile(_CONNECTED_USER.getLogin());
	}

	/**
	 * 
	 * @param missionID
	 * @param collaboratorID
	 * @param clientID
	 * @param missionTitle
	 * @param missionStartDate
	 * @param missionEndDate
	 * @param missionDescription
	 * @param missionSkillsList
	 */

	public static void updateMission(@Required int updateMissionID,
			@Required int updateClientID, @Required String updateMissionTitle,
			@Required String updateMissionStartDate,
			String updateMissionEndDate, String updateMissionDescription,
			String updateMissionSkillsList) {

		// 1. Retrieves the concerned mission
		Mission mission = Mission.findById(updateMissionID);
		if (mission == null) {
			error("Bad argument !");
			return;
		}

		// 2. Update fields
		Client client = Client.findById(updateClientID);
		mission.setClient(client);
		mission.setTitle(updateMissionTitle);
		try {
			Date startDate = Tools._DATE_FORMATTER
					.parse(updateMissionStartDate);
			Date endDate = Tools._DATE_FORMATTER.parse(updateMissionEndDate);
			if (startDate != null && endDate != null
					&& startDate.before(endDate) == false) {
				error("La date de début doit etre anterieure à la date de commencement de la mission !");
			}
			mission.setStartDate(startDate);
			mission.setEndDate(endDate);
		} catch (ParseException e) {
			e.printStackTrace();
		}
		mission.setDescription(updateMissionDescription);

		// Set skills
		mission.getMissionSkills().clear();
		String[] tempStringList = updateMissionSkillsList.split("[|]");
		for (String skillName : tempStringList) {
			Skill skill = models.Skill.find("name = ?", skillName).first();
			if (skill == null) {
				continue;
			}
			mission.addSkill(skill);
		}

		// 3. Save the updated mission entity
		mission.merge();
		mission.save();

		// 4. Logging
		Logger.debug(
				"[COLLABORATOR] Collaborator (%s) missions : one updated (title : %s).",
				_CURRENT_COLLABORATOR.getUser().getLogin(), updateMissionTitle);

		// 5. Redirect the client to the collaborator profile
		profile(_CONNECTED_USER.getLogin());
	}

	/**
	 * Delete a mission
	 * 
	 * @param missionID
	 */

	public static void deleteMission(String missionID) {
		// 0. Retrieve the mission by its ID
		Mission mission = Mission.findById(Integer.parseInt(missionID));

		// 1. Check if the mission is assumed by the current collaborator
		if (_CURRENT_COLLABORATOR.getValidProfile().getMissions()
				.contains(mission) == false) {
			error("Bad request !");
		}

		// 2. Logging
		Logger.debug(
				"[COLLABORATOR] Collaborator (%s) missions : one deleted (title : %s).",
				_CURRENT_COLLABORATOR.getUser().getLogin(), mission.getTitle());

		// 3. Delete the entity
		mission.delete();
	}

	// //////////////////////////////////////////////////////////
	// //// COLLABORATOR FORMATIONS MANAGEMENT (SYNCHRONOUSLY)
	// //////////////////////////////////////////////////////////

	/**
	 * Method which can retrieve data from view in order to create a
	 * collaborator formation
	 * 
	 * @param collaboratorID
	 * @param formationTitle
	 * @param formationStartDate
	 * @param formationEndDate
	 * @param formationEstablishment
	 * @param formationSkillsList
	 */

	public static void addFormation(@Required int collaboratorID,
			@Required String formationTitle,
			@Required String formationStartDate, String formationEndDate,
			@Required String formationEstablishment, String formationSkillsList) {

		// 1. Parse dates
		Date startDate = null;
		Date endDate = null;
		try {
			startDate = Tools._DATE_FORMATTER.parse(formationStartDate);
			endDate = Tools._DATE_FORMATTER.parse(formationEndDate);
			if (startDate != null && endDate != null
					&& startDate.before(endDate) == false) {
				error("La date de début doit etre anterieure à la date de commencement de la formation !");
			}
		} catch (ParseException e) {
			e.printStackTrace();
		}

		// 2. Add the new formation to the collaborator profile
		models.Collaborator collaborator = models.Collaborator
				.findById(collaboratorID);
		models.Formation formation = new models.Formation(formationTitle,
				startDate, endDate, formationEstablishment);

		// 2.1. Add specified skills to the formation
		String[] tempStringList = formationSkillsList.split("[|]");
		Set<Skill> newSkillList = new HashSet<Skill>();
		for (String skillName : tempStringList) {
			Skill skill = models.Skill.find("name = ?", skillName).first();
			if (skill == null) {
				continue;
			}
			newSkillList.add(skill);
		}
		formation.setFormationSkills(newSkillList);
		formation.save();

		// 2.2 Add the formation to the collaborator profile
		collaborator.getValidProfile().addFormation(formation);

		// 3. Save the entity in order to persists it into the DB
		collaborator.save();

		// 4. Logging
		Logger.debug(
				"[COLLABORATOR] Collaborator (%s) formations : one more added (title : %s).",
				_CURRENT_COLLABORATOR.getUser().getLogin(), formationTitle);

		// 5. Redirect the client to the collaborator profile
		profile(_CONNECTED_USER.getLogin());

	}

	/**
	 * Update a formation
	 * 
	 * @param updateFormationID
	 * @param updateFormationTitle
	 * @param updateFormationStartDate
	 * @param updateFormationEndDate
	 * @param updateFormationEstablishment
	 * @param updateFormationSkillsList
	 */

	public static void updateFormation(@Required String updateFormationID,
			String updateFormationTitle, String updateFormationStartDate,
			String updateFormationEndDate, String updateFormationEstablishment,
			String updateFormationSkillsList) {

		// Retrieves the concerned formation
		Formation formation = Formation.findById(Integer
				.parseInt(updateFormationID));
		if (formation == null) {
			error("Bad argument !");
			return;
		}

		// Update data
		formation.setTitle(updateFormationTitle);
		try {
			Date startDate = Tools._DATE_FORMATTER
					.parse(updateFormationStartDate);
			Date endDate = Tools._DATE_FORMATTER.parse(updateFormationEndDate);
			if (startDate != null && endDate != null
					&& startDate.before(endDate) == false) {
				Logger.error(
						"[COLLABORATOR] Collaborator (%s) formations : error during update ('%s').",
						_CURRENT_COLLABORATOR.getUser().getLogin(),
						"La date de début doit etre anterieure à la date de commencement de la formation !");
				error("La date de début doit etre anterieure à la date de commencement de la formation !");
			}
			formation.setStartDate(startDate);
			formation.setEndDate(endDate);
		} catch (ParseException e) {
			e.printStackTrace();
		}
		formation.setEstablishment(updateFormationEstablishment);

		// Skills
		String[] tempStringList = updateFormationSkillsList.split("[|]");
		Set<Skill> newSkillList = new HashSet<Skill>();
		for (String skillName : tempStringList) {
			Skill skill = models.Skill.find("name = ?", skillName).first();
			if (skill == null) {
				continue;
			}
			newSkillList.add(skill);
		}
		formation.setFormationSkills(newSkillList);

		// Save the entity
		formation.merge();
		formation.save();

		// Logging
		Logger.debug(
				"[COLLABORATOR] Collaborator (%s) formations : one more added (title : %s).",
				_CURRENT_COLLABORATOR.getUser().getLogin(),
				updateFormationTitle);

		// Redirect the client to the profile
		profile(_CONNECTED_USER.getLogin());
	}

	/**
	 * Delete a formation
	 * 
	 * @param formationID
	 */

	public static void deleteFormation(String formationID) {
		// 0. Retrieve the formation by its ID
		Formation formation = Formation.findById(Integer.parseInt(formationID));

		// 1. Check if the formation is assumed by the current collaborator
		if (_CURRENT_COLLABORATOR.getValidProfile().getFormations()
				.contains(formation) == false) {
			error("Bad request !");
		}

		// 2. Logging
		Logger.debug(
				"[COLLABORATOR] Collaborator (%s) formations : one deleted (title : %s).",
				_CURRENT_COLLABORATOR.getUser().getLogin(),
				formation.getTitle());

		// 3. Delete the entity
		formation.delete();
	}

	// ///////////////////////////////////////////
	// //// LANGUAGES MANAGEMENT (ASYNCHRONOUSLY)
	// ///////////////////////////////////////////

	/**
	 * Add languages to the profile
	 * 
	 * @param languages
	 */

	public static void updateProfileLanguages(String langs) {

		// Delete all existing languages associated to the current
		// collaborator's profile
		ProfileLanguages.delete("profile = ?",
				_CURRENT_COLLABORATOR.getValidProfile());

		// Parse JSON objects as JSONLanguage POJO
		Map<String, JSONLanguage> jsonLanguages = new Gson().fromJson(langs,
				new TypeToken<Map<String, JSONLanguage>>() {
				}.getType());

		// Add new languages to the profile
		for (JSONLanguage jsonLanguage : jsonLanguages.values()) {
			Language jpaLanguage = Language.find("name = ?", jsonLanguage.lang)
					.first();
			ProfileLanguages profileLang = new ProfileLanguages(
					_CURRENT_COLLABORATOR.getValidProfile(), jpaLanguage,
					jsonLanguage.level);
			try {
				profileLang.save();
			} catch (Exception e) {
				// Return error response (false)
				renderJSON(false);
			}
		}

		// Logging
		Logger.debug("[COLLABORATOR] Collaborator (%s) languages : updated.",
				_CURRENT_COLLABORATOR.getUser().getLogin(), langs);

		// Return success response (true)
		renderJSON(true);
	}

	// ///////////////////////////////////////////
	// //// AJAX METHOD
	// ///////////////////////////////////////////

	/**
	 * Get all nationalities
	 * 
	 * @return
	 */

	public static void nationalitiesToJSON() {
		List<Nationality> nationalities = Nationality.findAll();
		renderJSON(nationalities);
	}

	/**
	 * Returns a JSON formatted list of skills (called dynamically from client
	 * for auto complete fields)
	 */

	public static void skillsToJSON() {
		List<Skill> skills = Skill.findAll();
		renderJSON(skills);
	}

	/**
	 * Return a mission formated into JSON format
	 * 
	 * @param missionID
	 */

	public static void missionToJSON(int missionID) {
		Mission mission = Mission.findById(missionID);
		if (mission == null) {
			return;
		}
		renderJSON(mission);
	}

	/**
	 * Return a formation formated into JSON format
	 * 
	 * @param formationID
	 */

	public static void formationToJSON(int formationID) {
		Formation formation = Formation.findById(formationID);
		if (formation == null) {
			return;
		}
		renderJSON(formation);
	}

	/**
	 * Returns a list of skills concerning a specific mission
	 * 
	 * @param missionID
	 */

	public static void missionSkillsToJSON(int missionID) {
		Mission mission = Mission.findById(missionID);
		Set<Skill> skills = mission.getMissionSkills();
		if (mission == null || skills == null) {
			return;
		}
		renderJSON(skills);
	}

	/**
	 * Returns a list of skills concerning a specific formation
	 * 
	 * @param formationID
	 */

	public static void formationSkillsToJSON(int formationID) {
		Formation formation = Formation.findById(formationID);
		Set<Skill> skills = formation.getFormationSkills();
		if (formation == null || skills == null) {
			return;
		}
		renderJSON(skills);
	}

	/**
	 * Returns the list of all languages
	 */

	public static void languagesToJSON() {
		List<Language> languages = Language.findAll();
		if (languages == null || languages.isEmpty()) {
			return;
		}
		renderJSON(languages);
	}

	/**
	 * Returns the list of languages associated to the current profile
	 */

	public static void profileLanguagesToJSON() {
		List<ProfileLanguages> languages = ProfileLanguages.find("profile = ?",
				_CURRENT_COLLABORATOR.getValidProfile()).fetch();
		if (languages == null || languages.isEmpty()) {
			renderJSON(false);
			return;
		}
		renderJSON(languages);
	}

	/**
	 * Find notifications for the current user and push it to the client
	 */

	public static void findNotifications() {

		// Logging
		Logger.debug("[COLLABORATOR] Refreshing notifications list...");

		// Retrieves all notifications concerned by the current user
		List<Notification> notifications = Notification.find("user = ?",
				_CONNECTED_USER).fetch();
		if (notifications == null || notifications.isEmpty()) {
			return;
		}

		// Push notification objects to the client
		renderJSON(notifications);
	}

	public static void deleteNotification(int notificationID) {
		// Logging
		Logger.debug("[COLLABORATOR] Deleting notifications (id : %d)...",
				notificationID);

		// Retrieves the notification
		Notification notification = Notification.findById(notificationID);
		if (notification == null) {
			return;
		}

		// Delete it
		notification.delete();
	}
}
